package cn.jsu.oj.dispatchServer.satellite.handler.patrol;

import cn.jsu.oj.dispatchServer.satellite.handler.patrol.hard.*;
import cn.jsu.oj.dispatchServer.satellite.handler.patrol.hard.Cpu;
import lombok.extern.slf4j.Slf4j;
import org.hyperic.sigar.*;
import org.hyperic.sigar.Swap;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

/**
 * 服务器侦察兵
 *
 * @author shan
 * @date 2022/07/03
 */
@Slf4j
@Component
public class ServerPatrol {

    public List<Cpu> detectCpu() {
        Integer count = getCpuCount();
        List<CpuBase> cpuBaseList = getCpuBase();
        List<CpuUse> cpuUseList = getCpuUse();
        return refractCpu(count, cpuBaseList, cpuUseList);
    }
    
    public List<Cpu> refractCpu(Integer count, List<CpuBase> cpuBaseList, List<CpuUse> cpuUseList) {
        List<Cpu> cpuList = new ArrayList<>();
        for (int i = 0; i < count; i ++) {
            Cpu cpu = new Cpu(cpuBaseList.get(i), cpuUseList.get(i));
            cpuList.add(cpu);
        }
        return cpuList;
    }
    
    private List<CpuBase> getCpuBase() {
        List<CpuBase> cpuBaseList = new ArrayList<>();
        CpuInfo[] cpus = getCpu();
        for (CpuInfo cpu : cpus) {
            CpuBase cpuBase = new CpuBase(
                    cpu.getMhz(),
                    cpu.getVendor(),
                    cpu.getModel(),
                    cpu.getCacheSize()
            );
            cpuBaseList.add(cpuBase);
        }
        return cpuBaseList;
    }
    
    private List<CpuUse> getCpuUse() {
        List<CpuUse> cpuUseList = new ArrayList<>();
        CpuPerc[] cpuStatus = getCpuStatus();
        for (CpuPerc cpuPerc : cpuStatus) {
            CpuUse cpuUse = new CpuUse(
                    cpuPerc.getUser(),
                    cpuPerc.getSys(),
                    cpuPerc.getWait(),
                    cpuPerc.getNice(),
                    cpuPerc.getIdle(),
                    cpuPerc.getCombined()
            );
            cpuUseList.add(cpuUse);
        }
        return cpuUseList;
    }
    
    private Integer getCpuCount() {
        Sigar sigar = new Sigar();
        int cpuCount = -1;
        try {
            cpuCount = sigar.getCpuInfoList().length;
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return cpuCount;
    }

    private CpuInfo[] getCpu() {
        Sigar sigar = new Sigar();
        CpuInfo[] cpuInfos = null;
        try {
            cpuInfos = sigar.getCpuInfoList();
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return cpuInfos;
    }

    private CpuPerc[] getCpuStatus() {
        Sigar sigar = new Sigar();
        CpuPerc[] cpuStatus = new CpuPerc[0];
        try {
            cpuStatus = sigar.getCpuPercList();
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return cpuStatus;
    }
    
    public Storage detectStorage() {
        Mem mem = getMem();
        return new Storage(
                mem.getTotal() / 1024L, 
                mem.getUsed() / 1024L, 
                mem.getFree() / 1024L
        );
    }

    private Mem getMem() {
        Sigar sigar = new Sigar();
        Mem mem = null;
        try {
            mem = sigar.getMem();
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return mem;
    }

    public cn.jsu.oj.dispatchServer.satellite.handler.patrol.hard.Swap detectSwap() {
        Swap swap = getSwap();
        return new cn.jsu.oj.dispatchServer.satellite.handler.patrol.hard.Swap(
                swap.getTotal() / 1024L, 
                swap.getUsed() / 1024L, 
                swap.getFree() / 1024L
        );
    }

    private Swap getSwap() {
        Sigar sigar = new Sigar();
        Swap swap = null;
        try {
            swap = sigar.getSwap();
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return swap;
    }
    
    public Os detectOs() {
        OperatingSystem os = getOs();
        return new Os(
                os.getArch(), 
                os.getCpuEndian(), 
                os.getDataModel(), 
                os.getDescription(), 
                os.getVendor(), 
                os.getVendorName(), 
                os.getVersion()
        );
    }

    private OperatingSystem getOs() {
        return OperatingSystem.getInstance();
    }

    public List<Disk> detectDisk() {
        FileSystem[] fileSystems = getDisk();
        List<Disk> diskList = new ArrayList<>();
        for (FileSystem fileSys :  fileSystems) {
            FileSystemUsage usage = getDiskUsage(fileSys);
            Disk disk = new Disk(
                    fileSys.getDevName(), 
                    fileSys.getTypeName(), 
                    fileSys.getType(), 
                    usage.getTotal(), 
                    usage.getFree(), 
                    usage.getAvail(), 
                    usage.getUsed(), 
                    usage.getUsePercent()
            );
            diskList.add(disk);
        }
        return diskList;
    }
    
    private FileSystem[] getDisk() {
        Sigar sigar = new Sigar();
        FileSystem[] disks = null;
        try {
            disks = sigar.getFileSystemList();
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return disks;
    }

    private FileSystemUsage getDiskUsage(FileSystem fileSys) {
        Sigar sigar = new Sigar();
        FileSystemUsage usage = null;
        try {
            usage = sigar.getFileSystemUsage(fileSys.getDirName());
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        return usage;
    }
    
    public List<Net> detectNet() {
        NetInterfaceStat[] netStats = getNet();
        List<Net> netList = new ArrayList<>();
        for (NetInterfaceStat netStat : netStats) {
            Net net = new Net(
                    netStat.getRxPackets(), 
                    netStat.getTxPackets(), 
                    netStat.getRxBytes(), 
                    netStat.getTxBytes(), 
                    netStat.getRxErrors(), 
                    netStat.getTxBytes(), 
                    netStat.getRxDropped(), 
                    netStat.getTxDropped()
            );
            netList.add(net);
        }
        return netList;
    }

    private NetInterfaceStat[] getNet() {
        Sigar sigar = new Sigar();
        List<NetInterfaceStat> netList = new ArrayList<>();
        try {
            String[] ifNames = sigar.getNetInterfaceList();
            for (String name : ifNames) {
                NetInterfaceStat netStat = sigar.getNetInterfaceStat(name);
                netList.add(netStat);
            }
        } catch (SigarException e) {
            e.printStackTrace();
        } finally {
            sigar.close();
        }
        NetInterfaceStat[] nets = new NetInterfaceStat[netList.size()];
        netList.toArray(nets);
        return nets;
    }
    
    public List<Gpu> detectGpu() {
        String[][] gpus = getGpu();
        List<Gpu> gpuList = new ArrayList<>();
        for (String[] allInfo : gpus) {
            String[] strings = allInfo[1].split("\\|")[1].split("\\s+");
            StringBuilder gpuName = new StringBuilder();
            for (int i = 0; i < strings.length - 1; i ++) {
                if (i <= 1) {
                    continue;
                }
                gpuName.append(strings[i]).append(" ");
            }
            String[] subInfo = allInfo[2].split("\\|")[2].split("\\s+");
            Double useMem = Double.parseDouble(subInfo[1].split("MiB")[0]);
            Double totalMem = Double.parseDouble(subInfo[3].split("MiB")[0]);
            Gpu gpu = new Gpu(
                    gpuName.toString(), 
                    useMem, 
                    totalMem, 
                    totalMem - useMem, 
                    useMem / totalMem
            );
            gpuList.add(gpu);
        }
        return gpuList;
    }

    private String[][] getGpu() {
        Process process = getProcess();
        String[] gpus = getGpus(process);
        String[][] gpuInfos = new String[gpus.length - 1][];
        for (int i = 0; i < gpus.length - 1; i++) {
            String[] info = gpus[i].split("\n");
            gpuInfos[i] = info;
        }
        return gpuInfos;
    }

    private Process getProcess() {
        Map<String, Object> map = osMap();
        String win = "windows", lin = "linux";
        String os = osJudge();
        Process process = null;
        try {
            if (win.equals(os)) {
                process = Runtime.getRuntime().exec((String) map.get(os));
            } else if (lin.equals(os)) {
                process = Runtime.getRuntime().exec((String[]) map.get(os));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return process;
    }

    private Map<String, Object> osMap() {
        Map<String, Object> map = new HashMap<>(2);
        map.put("windows", "nvidia-smi.exe");
        map.put("linux", new String[]{"/bin/bash", "-c", "nvidia-smi"});
        return map;
    }

    private String osJudge() {
        String string = "os.name", win = "window", lin = "linux";
        if (System.getProperty(string).toLowerCase(Locale.ROOT).contains(win)) {
            return "windows";
        }
        if (System.getProperty(string).toLowerCase(Locale.ROOT).contains(lin)) {
            return "windows";
        }
        return null;
    }

    private String[] getGpus(Process process) {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
        StringBuilder buffer = new StringBuilder();
        String line = "";
        while (true) {
            try {
                if ((line = reader.readLine()) == null) {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            buffer.append(line).append("\n");
        }
        String gpu = buffer.toString();
        if (buffer.charAt(gpu.length() - 1) == '\n') {
            gpu = gpu.substring(0, gpu.length() - 2);
        }
        String[] split = gpu.split("\\|===============================\\+======================\\+======================\\|");
        String[] drop = split[1].split(" {79}");
        return drop[0].split("\\+-------------------------------\\+----------------------\\+----------------------\\+");
    }
}
